#ifndef DIBUJABLE_H_
#define DIBUJABLE_H_

#include <GL/glew.h>
#include <GL/freeglut.h>
#include <glm/glm.hpp>
#include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/transform2.hpp>
#include <glm/gtx/projection.hpp>
#include <iostream>
#include <fstream>
#include "Camara.h"
#include "../CargadorDeTextures.h"
#include <string.h>
#include <SOIL/SOIL.h>
class Dibujable {

public:
	 Dibujable()
	{
	}
	 Dibujable(const char* vertexShader, const char* fragShader){		crearShaders(vertexShader, fragShader);
	}
	virtual void render(GLuint handle, glm::mat4* matModelo) = 0;
	virtual void render(GLuint handle) = 0;
	virtual ~Dibujable() {
	}
	void loadAndInitTexture(const char* filename);
	void crearShaders(const char* vertexShader, const char* fragShader);
	GLuint loadShader(GLenum type, const char *shaderSrc);
	void renderMatrix(GLuint handle, glm::mat4* matModelo);
	virtual void setCam(Camara* cam, glm::mat4 proyeccion);
	virtual void setTextureid(GLuint textureid) {
		this->textureid = textureid;
	}

	void setHandle(GLuint handle) {
		this->handle = handle;
	}

	void setTextureNormalId(GLuint textureNormalId);

	void setTextureReflexionId(GLuint textureReflexionId) {
		this->textureReflexionId = textureReflexionId;
	}

	;
private:
protected:
	GLuint textureid;
	GLuint textureNormalId;
	GLuint textureReflexionId;
	GLuint tipoTexture;
	GLuint handle;
	Camara* cam;
	glm::mat4 proyeccion;
};


inline void Dibujable::loadAndInitTexture(const char* filename) {
	 {
	     // Load texture file
	    	int image_witdh;
	 	int image_height;
	 	int image_channels;
	 	unsigned char* image_buffer  = SOIL_load_image(filename, &image_witdh, &image_height, &image_channels, SOIL_LOAD_RGBA);

	     // Copy file to OpenGL
	     glActiveTexture(GL_TEXTURE0);
	     glGenTextures(1, &this->textureid);
	     glBindTexture(GL_TEXTURE_2D, this->textureid);
//	     gluBuild2DMipmaps( GL_TEXTURE_2D, 3, image_witdh, image_height, GL_RGB, GL_UNSIGNED_BYTE, image_buffer );
	     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_witdh, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_buffer);
	     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	     // Set the Tex1 sampler uniform to refer to texture unit 0
//	     int loc = glGetUniformLocation(this->handle, "Tex1");

//	     if( loc >= 0 )
//	     {
//	 		// We indicate that Uniform Variable sampler2D "text" uses  Texture Unit 0
//	         glUniform1i(loc, 0);
//	     }
//	     else
//	     {
//	         fprintf(stderr, "Uniform variable Tex1 not found!\n");
//	     }
	 }
}

inline void Dibujable::crearShaders(const char* vertexShaderRuta,
		const char* fragShaderRuta) {
		GLuint vertShader = glCreateShader (GL_VERTEX_SHADER);
	        if ( 0 == vertShader )
	        {
	            std::cout << "Error creating vertex shader" << std::endl;
	        }

	        std::ifstream v_shader_file(vertexShaderRuta, std::ifstream::in);
	        std::string v_str((std::istreambuf_iterator<char>(v_shader_file)), std::istreambuf_iterator<char>());
	        const char* vs_code_array[] = {v_str.c_str()};

	        glShaderSource( vertShader, 1, vs_code_array, NULL);

	        // Compilar el shader
	        glCompileShader( vertShader );

	        // verificar resultado de la compilacion
	        GLint vs_compilation_result;
	        glGetShaderiv( vertShader, GL_COMPILE_STATUS, &vs_compilation_result );
	        if( GL_FALSE == vs_compilation_result )
	        {
	            std::cout << "Vertex shader compilation failed!\n" << std::endl;
	            GLint logLen;
	            glGetShaderiv( vertShader, GL_INFO_LOG_LENGTH, &logLen );
	            if( logLen > 0 )
	            {
	                char * log = (char *)malloc(logLen);
	                GLsizei written;
	                glGetShaderInfoLog(vertShader, logLen, &written, log);
	                std::cout << "Shader log: " << log << std::endl;
	                free(log);
	            }
	        }

	        // Load fragment Shader
	                GLuint fragShader = glCreateShader (GL_FRAGMENT_SHADER);
	                if ( 0 == fragShader )
	                {
	                    std::cout << "Error creating fragment shader" << std::endl;
	                }

	                std::ifstream f_shader_file(fragShaderRuta, std::ifstream::in);
	                std::string f_str((std::istreambuf_iterator<char>(f_shader_file)), std::istreambuf_iterator<char>());
	                const char* fs_code_array[] = {f_str.c_str()};

	                glShaderSource( fragShader, 1, fs_code_array, NULL);

	                // Compilar el shader
	                glCompileShader( fragShader );

	                // verificar resultado de la compilacion
	                GLint fs_compilation_result;
	                glGetShaderiv( fragShader, GL_COMPILE_STATUS, &fs_compilation_result );
	                if( GL_FALSE == fs_compilation_result )
	                {
	                    std::cout << "Fragment shader compilation failed!\n" << std::endl;
	                    GLint logLen;
	                    glGetShaderiv( fragShader, GL_INFO_LOG_LENGTH, &logLen );
	                    if( logLen > 0 )
	                    {
	                        char * log = (char *)malloc(logLen);
	                        GLsizei written;
	                        glGetShaderInfoLog( fragShader, logLen, &written, log);
	                        std::cout << "Shader log: " << log << std::endl;
	                        free(log);
	                    }
	                }
	        	// *******************************************

	            // *******************************************
	            // Linking the shader programms
	            // *******************************************
	                this->handle = glCreateProgram();
	                if ( 0 == this->handle )
	                {
	                    std::cout << "Error creating program object" << std::endl;
	                }
	                else
	                {
	                    glAttachShader( this->handle, vertShader );
	                    glAttachShader( this->handle, fragShader );

	                    glLinkProgram( this->handle );

	                    GLint status;
	                    glGetProgramiv( this->handle, GL_LINK_STATUS, &status );
	                    if( GL_FALSE == status )
	                    {
	                        std::cout << "Failed to link shader program!\n" << std::endl;
	                        GLint logLen;
	                        glGetProgramiv( this->handle, GL_INFO_LOG_LENGTH, &logLen);
	                        if( logLen > 0 )
	                        {
	                            char * log = (char *)malloc(logLen);
	                            GLsizei written;
	                            glGetProgramInfoLog(handle, logLen, &written, log);
	                            std::cout << "Program log: \n%s" << std::endl;
	                            free(log);
	                        }
	                    }
	                    else
	                    {
	                        glUseProgram( this->handle );
	                    }
	                }

	}

inline GLuint Dibujable::loadShader(GLenum type, const char* shaderSrc) {
	GLuint shader;
	   GLint compiled;

	   // Create the shader object
	   shader = glCreateShader(type);

	   if(shader == 0)
	   return 0;

	   shaderSrc = "vertexCuadrado.vert";
	   int len = strlen(shaderSrc);
	   // Load the shader source
	    glShaderSource(shader, 1, &shaderSrc, &len);

	    // Compile the shader
	    glCompileShader(shader);
	    // Check the compile status
	      glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);

	      if(!compiled)
	      {
	         GLint infoLen = 0;

	         glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);

	         if(infoLen > 1)
	         {
	            char* infoLog = (char*)malloc(sizeof(char) * infoLen);

	            glGetShaderInfoLog(shader, infoLen, NULL, infoLog);
	            printf("Error compiling shader:\n%s\n", infoLog);

	            free(infoLog);
	         }

	         glDeleteShader(shader);
	         return 0;
	      }

	      return shader;


}

inline void Dibujable::renderMatrix(GLuint handle, glm::mat4* matModelo) {
	glUseProgram(handle );

		    // Bind View Matrix
		    this->cam->bindViewCamara(handle);

		    // Bind Projection MAtrix
		    GLuint location_projection_matrix = glGetUniformLocation( handle, "ProjectionMatrix");
		    if( location_projection_matrix >= 0 )
			{
				glUniformMatrix4fv( location_projection_matrix, 1, GL_FALSE, &this->proyeccion[0][0]);
			}
		    //
		    ///////////////////////////////////////////



		    //////////////////////////////////////
		    // Bind Light Settings
		    glm::vec4 light_position = glm::vec4( 180.0f, 180.0f, 180.0f, 1.0f );
		    glm::vec3 light_intensity = glm::vec3( 1.0f, 1.0f, 1.0f );

		    GLuint location_light_position = glGetUniformLocation( handle, "LightPosition");
		    if( location_light_position >= 0 )
			{
		        glUniform4fv( location_light_position, 1, &light_position[0]);
			}

		    GLuint location_light_intensity = glGetUniformLocation( handle, "Ld");
		    if( location_light_intensity >= 0 )
			{
				glUniform3fv( location_light_intensity, 1, &light_intensity[0]);
			}
}

inline void Dibujable::setCam(Camara* cam, glm::mat4 proyeccion) {
	this->cam = cam;
	this->proyeccion = proyeccion;
}

inline void Dibujable::setTextureNormalId(GLuint textureNormalId) {
	this->textureNormalId = textureNormalId;
}

#endif
